﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Server.cs" company="DarkBone">
//   DarkBone
// </copyright>
// <summary>
//   Defines Server
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace DarkBone.Engine.Networking
{
   using System;
   using System.Net;
   using System.Net.Sockets;
   using System.Threading;

   /// <summary>
   /// Networking Class
   /// </summary>
   public sealed class Server
   {
      #region Constants and Fields

      /// <summary>
      /// Multicast port
      /// </summary>
      public const int MulticastPort = 0xDEAD;

      /// <summary>
      /// Multicast port
      /// </summary>
      public const int UnicastPort = 0xCAFE;

      /// <summary>
      /// Multicast address
      /// </summary>
      /// "224.0.105.5"
      public const uint MulticastAddress = 5 << 24 | 105 << 16 | 0 << 8 | 224 << 0;

      /// <summary>
      /// Called on receive object
      /// </summary>
      private readonly OnReceiveCallback onReceive;

      /// <summary>
      /// Thread for networking communication
      /// </summary>
      private readonly Thread thrBroadcast;

      /// <summary>
      /// Thread for networking communication
      /// </summary>
      private readonly Thread thrListen;

      /// <summary>
      /// Reset event
      /// </summary>
      private readonly ManualResetEvent locker = new ManualResetEvent(false);

      /// <summary>
      /// Continue beating
      /// </summary>
      private bool alive;

      /// <summary>
      /// Tcp Listener
      /// </summary>
      private Socket listener;

      #endregion

      #region Constructors and Destructors

      /// <summary>
      /// Initializes a new instance of the <see cref="Server"/> class.
      /// Starts threads
      /// </summary>
      /// <param name="onReceive">The on Receive.</param>
      public Server(OnReceiveCallback onReceive)
      {
         this.onReceive = onReceive;
         this.thrBroadcast = new Thread(this.MulticastAnnounceThreadProc);
         this.thrListen = new Thread(this.ListenThreadProc);
         this.alive = true;
         this.thrBroadcast.Start();
         this.thrListen.Start();
      }

      /// <summary>
      /// Finalizes an instance of the <see cref="Server"/> class. 
      /// Stops all threads
      /// </summary>
      ~Server()
      {
         this.alive = false;
         this.thrBroadcast.Join();
         this.thrListen.Join();
      }

      #endregion

      #region Delegates

      /// <summary>
      /// Callback delegate
      /// </summary>
      /// <param name="obj">
      /// Received Object
      /// </param>
      /// <returns>
      /// Returned object
      /// </returns>
      public delegate object OnReceiveCallback(object obj);

      #endregion

      #region Public Methods

      /// <summary>
      /// Stops networking
      /// </summary>
      public void Stop()
      {
         this.alive = false;
      }

      #endregion

      #region Methods

      /// <summary>
      /// Listen on all interfaces for TCP requests
      /// </summary>
      private void ListenThreadProc()
      {
         this.listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
         this.listener.Bind(new IPEndPoint(IPAddress.Any, UnicastPort));
         while (this.alive)
         {
            this.locker.Reset();
            this.listener.Listen(4);
            this.listener.BeginAccept(this.OnAccept, this.listener);
            this.locker.WaitOne();
         }
      }

      /// <summary>
      /// Starts when an incomming connection was requested
      /// </summary>
      /// <param name="result">
      /// The result.
      /// </param>
      private void OnAccept(IAsyncResult result)
      {
         var message = new Message { Done = this.OnDone, Socket = ((Socket)result.AsyncState).EndAccept(result) };
         this.locker.Set();
         message.Locker.Reset();
         message.Socket.BeginReceive(message.Buffer, 0, message.Buffer.Length, SocketFlags.None, Machinery.OnReceive, message);
      }

      /// <summary>
      /// Deserializes and outputs the received object
      /// </summary>
      /// <param name="message">The message.</param>
      private void OnDone(Message message)
      {
         var serializedObject = Serializator.SerializeObj(this.OnReceive(Serializator.DeserializeObj<object>(message.TransmissionBuffer.ToArray())));
         message.Clean();
         var buffer = new byte[serializedObject.Buffer.Length + 4];
         Array.Copy(BitConverter.GetBytes(serializedObject.Buffer.Length), buffer, 4);
         Array.Copy(serializedObject.Buffer, 0, buffer, 4, serializedObject.Buffer.Length);
         message.Locker.Reset();
         message.Length = buffer.Length;
         message.Socket.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, Machinery.OnSend, message);
         message.Locker.WaitOne();
         message.Clean();
         message.Locker.Reset();
         message.Socket.BeginReceive(message.Buffer, 0, message.Buffer.Length, SocketFlags.None, Machinery.OnReceive, message);
      }

      /// <summary>
      /// Multicast send thread procedure
      /// </summary>
      /// <param name="obj">
      /// The argument for call.
      /// </param>
      private void MulticastAnnounceThreadProc(object obj)
      {
         var hostName = Dns.GetHostName();
         var info = Serializator.SerializeObj(new HostInfo(hostName, Dns.GetHostEntry(hostName), UnicastPort));
         using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
         {
            var ipmulticast = new IPAddress(MulticastAddress);
            socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(ipmulticast));
            socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 1);
            var ipep = new IPEndPoint(ipmulticast, MulticastPort);
            socket.Connect(ipep);
            while (this.alive)
            {
               socket.Send(info.Buffer);
               Thread.Sleep(2000);
            }

            socket.Close();
         }
      }

      /// <summary>
      /// Connects to server
      /// </summary>
      /// <param name="obj">Host info</param>
      /// <returns>
      /// The object returned by server.
      /// </returns>
      private object OnReceive(object obj)
      {
         return this.onReceive != null ? this.onReceive(obj) : obj;
      }

      #endregion
   }
}